package com.run_java_test.test_run;

import jakarta.annotation.PreDestroy;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;

@RequestMapping("up")
@RestController
public class UploadCodeController2 {

    private static final String BASE_PATH = "D:\\a2software_cache\\IDEAProject\\self\\java_study_springboot_run_java_source_code\\dynamic_classes";

    @Autowired
    private RequestMappingHandlerMapping handlerMapping;

    @Autowired
    private GenericApplicationContext applicationContext;

    private URLClassLoader dynamicClassLoader;

    @PostMapping("/upload")
    public String uploadAndCompile(MultipartFile file) throws Exception {
        // Save the uploaded file
        String fileName = file.getOriginalFilename();
        if (fileName == null || !fileName.endsWith(".java")) {
            throw new IllegalArgumentException("Invalid file format. Only .java files are allowed.");
        }
        saveSourceFile(file);

        // Compile the file
        compileSourceFiles();

        // Load and register the compiled class
        loadAndRegisterClasses();

        return "File uploaded, compiled, and registered successfully.";
    }

    private void saveSourceFile(MultipartFile file) throws IOException {
        Files.createDirectories(Paths.get(BASE_PATH));
        File sourceFile = new File(BASE_PATH + file.getOriginalFilename());
        try (FileOutputStream fos = new FileOutputStream(sourceFile)) {
            fos.write(file.getBytes());
        }
    }

    private void compileSourceFiles() throws IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            throw new IllegalStateException("Java Compiler not available.");
        }

        File sourceDir = new File(BASE_PATH);
        File[] sourceFiles = sourceDir.listFiles((dir, name) -> name.endsWith(".java"));
        if (sourceFiles == null || sourceFiles.length == 0) {
            throw new IllegalStateException("No source files to compile.");
        }

        for (File sourceFile : sourceFiles) {
            int result = compiler.run(null, null, null, sourceFile.getPath());
            if (result != 0) {
                throw new IllegalStateException("Compilation failed for file: " + sourceFile.getName());
            }
        }
    }

    private void loadAndRegisterClasses() throws Exception {
        File classDir = new File(BASE_PATH);
        URL[] urls = {classDir.toURI().toURL()};
        if (dynamicClassLoader != null) {
            dynamicClassLoader.close();
        }
        dynamicClassLoader = new URLClassLoader(urls);

        File[] classFiles = classDir.listFiles((dir, name) -> name.endsWith(".class"));
        if (classFiles != null) {
            for (File classFile : classFiles) {
                String className = classFile.getName().replace(".class", "");
                Class<?> clazz = dynamicClassLoader.loadClass(className);
                unregisterController(clazz);
                registerControllerBean(clazz);
            }
        }
    }

    private void unregisterController(Class<?> clazz) {
        handlerMapping.getHandlerMethods().forEach((key, value) -> {
            if (value.getBeanType().equals(clazz)) {
                try {
                    handlerMapping.unregisterMapping(key);
                    System.out.println("Unregistered mapping: " + key);
                } catch (Exception e) {
                    System.err.println("Error unregistering mapping: " + key);
                }
            }
        });
    }

    private <T> void registerControllerBean(Class<T> clazz) {
        T controllerInstance;
        try {
            controllerInstance = clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("Unable to instantiate dynamic Controller class", e);
        }

        if (applicationContext.containsBeanDefinition(clazz.getSimpleName())) {
            applicationContext.removeBeanDefinition(clazz.getSimpleName());
            System.out.println("Removed old bean: " + clazz.getSimpleName());
        }

        applicationContext.registerBean(clazz.getSimpleName(), clazz, () -> controllerInstance);
        try {
            handlerMapping.afterPropertiesSet();
        } catch (Exception e) {
            throw new RuntimeException("Error refreshing HandlerMapping", e);
        }
    }

    @PreDestroy
    public void cleanup() {
        if (dynamicClassLoader != null) {
            try {
                dynamicClassLoader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
